package cn.mrcode.wxsdk.core.context;

import cn.mrcode.wxsdk.core.CoreInit;
import cn.mrcode.wxsdk.core.dialogue.common.PublicAccount;
import cn.mrcode.wxsdk.core.dialogue.common.config.InitConstans;
import cn.mrcode.wxsdk.core.dialogue.common.helper.Dom4jHelper;
import cn.mrcode.wxsdk.core.dialogue.common.util.CastUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXValidator;
import org.dom4j.io.XMLWriter;
import org.dom4j.util.XMLErrorHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;

/**
 * 配置文件解析
 *
 * @author zhuqiang
 * @version V1.0
 * @date 2016/6/16 10:08
 */
public class ParseConfig {
    private static Logger log = LoggerFactory.getLogger(CoreInit.class);
    private static String CONFIGNAME = "/initConfig.xml";

    public ParseConfig() {
        this(CONFIGNAME);
    }

    public ParseConfig(String configName) {
        if (StringUtils.isNotBlank(configName)) {
            CONFIGNAME = configName;
        }
    }

    /** 解析 */
    public ConfigContext parse() {
        try {
            validator(CONFIGNAME, "/initConfig.xsd");
            ConfigContext configContext = this.parseXMl();
            return configContext;
        } catch (Exception e) {
            log.error("解析配置文件失败！");
            throw new RuntimeException(e);
        }
    }

    /**
     * 校验 配置文件是否符合xsd规范
     *
     * @param xmlFileName 绝对路径
     * @param xsdFileName 绝对路径
     *
     * @return
     */
    private void validator(String xmlFileName, String xsdFileName) {
        try {
            //创建默认的XML错误处理器
            XMLErrorHandler errorHandler = new XMLErrorHandler();

            //获取基于 SAX 的解析器的实例
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //解析器在解析时验证 XML 内容。
            factory.setValidating(true);
            //指定由此代码生成的解析器将提供对 XML 名称空间的支持。
            factory.setNamespaceAware(true);
            //使用当前配置的工厂参数创建 SAXParser 的一个新实例。
            SAXParser parser = factory.newSAXParser();
            //创建一个读取工具
            SAXReader xmlReader = new SAXReader();
            //获取要校验xml文档实例
            Document xmlDocument = (Document) xmlReader.read(this.getClass().getResourceAsStream(xmlFileName));
            //设置 XMLReader 的基础实现中的特定属性。核心功能和属性列表可以在 [url]http://sax.sourceforge.net/?selected=get-set[/url] 中找到。
            parser.setProperty(
                    "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                    "http://www.w3.org/2001/XMLSchema");
            parser.setProperty(
                    "http://java.sun.com/xml/jaxp/properties/schemaSource",
                    this.getClass().getResourceAsStream("/initConfig.xsd"));
            //创建一个SAXValidator校验工具，并设置校验工具的属性
            SAXValidator validator = new SAXValidator(parser.getXMLReader());
            //设置校验工具的错误处理器，当发生错误时，可以从处理器对象中得到错误信息。
            validator.setErrorHandler(errorHandler);

            //校验
            validator.validate(xmlDocument);

            OutputFormat prettyPrint = OutputFormat.createPrettyPrint();
//           prettyPrint.setEncoding("gbk");// 默认是utf-8 如果乱码，打开
            XMLWriter writer = new XMLWriter(prettyPrint);
            //如果错误信息不为空，说明校验失败，打印错误信息
            if (errorHandler.getErrors().hasContent()) {
                System.out.println("XML文件通过XSD文件校验失败！");
                writer.write(errorHandler.getErrors());
                throw new RuntimeException(errorHandler.getErrors().toString());
            } else {
                System.out.println("Good! XML文件通过XSD文件校验成功！");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析xml文件资源
     *
     * @return
     *
     * @throws DocumentException
     */
    private ConfigContext parseXMl() throws DocumentException {
        InputStream inputStream = this.getClass().getResourceAsStream(CONFIGNAME);
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();

        ConfigContext configContext = new ConfigContext(parseIsDistributedMode(root));
        configContext.setPayPublicAccountMap(parsePayPublicAccountList(root));
        configContext.setDirstributedConfig(parseDistributedConfig(root, configContext.isDistributedMode()));
        configContext.setHttpClientClassName(parseHttpClientClassName(root));
        return configContext;
    }

    /**
     * 解析处理 支付公众号 信息
     *
     * @param root
     */
    private HashMap<String, PublicAccount> parsePayPublicAccountList(Element root) {
        HashMap<String, PublicAccount> payPublicAccountMap = null;
        Node payPublicAccountListNode = Dom4jHelper.selectSingleNode(root, InitConstans.node_payPublicAccountList);

        if (payPublicAccountListNode != null) {
            payPublicAccountMap = new HashMap<String, PublicAccount>();
            List<Node> account = payPublicAccountListNode.selectNodes(InitConstans.node_account);
            if (account.size() > 0) {
                for (Node node : account) {
                    List<Element> property = node.selectNodes(InitConstans.node_property);
                    HashMap<String, String> map = new HashMap<String, String>();
                    for (Element element : property) {
                        map.put(element.attributeValue("name"), element.attributeValue("value"));
                    }
                    PublicAccount publicAccount = new PublicAccount();
                    try {
                        BeanUtils.copyProperties(publicAccount, map);
                        payPublicAccountMap.put(publicAccount.getAppID(), publicAccount);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return payPublicAccountMap;
    }

    /**
     * 解析 mode 模式 属性
     *
     * @param root
     *
     * @return 返回模式，true 是分布式模式
     */
    private boolean parseIsDistributedMode(Element root) {
        String modeText = Dom4jHelper.selectSingleNodeText(root, InitConstans.node_mode);
        return CastUtil.castBoolean(modeText);
    }

    /**
     * 解析处理分布式模式的数据处理
     *
     * @param root
     * @param isDistributedMode
     */
    private ConfigContext.DistributedConfig parseDistributedConfig(Element root, boolean isDistributedMode) {
        if (isDistributedMode) {
            //分布式参数
            Node distributedConfigNode = Dom4jHelper.selectSingleNode(root, InitConstans.distributedConfig);
            if (distributedConfigNode == null) {
                throw new IllegalArgumentException("distributedConfig 参数不能为空");
            }

            Node sessionTimeoutNode = Dom4jHelper.selectSingleNode(distributedConfigNode, InitConstans.xPath_sessionTimeout);
            Node zkServiceListNode = Dom4jHelper.selectSingleNode(distributedConfigNode, InitConstans.xPath_zkServiceList);
            Node publicAccountListNode = Dom4jHelper.selectSingleNode(distributedConfigNode, InitConstans.xPath_publicAccountList);

            if (sessionTimeoutNode == null || zkServiceListNode == null || publicAccountListNode == null) {
                throw new IllegalArgumentException("sessionTimeout、zkServiceList、publicAccountList 参数不能为空（请检查参数）");
            }
            ConfigContext.DistributedConfig distributedConfig = new ConfigContext.DistributedConfig();
            distributedConfig.setSessionTimeout(CastUtil.castInt(Dom4jHelper.getAttributeValue(sessionTimeoutNode, "value"), 10000));
            distributedConfig.setZkServiceList(Dom4jHelper.getAttributeValue(zkServiceListNode, "value"));

            HashMap<String, PublicAccount> publicAccountMap = new HashMap<String, PublicAccount>();
            List<Node> account = publicAccountListNode.selectNodes(InitConstans.node_account);
            if (account.size() <= 0) {
                throw new IllegalArgumentException("account 至少必须有一个公众号");
            }

            for (Node node : account) {
                List<Element> property = node.selectNodes(InitConstans.node_property);
                HashMap<String, String> map = new HashMap<String, String>();
                for (Element element : property) {
                    map.put(element.attributeValue("name"), element.attributeValue("value"));
                }
                PublicAccount publicAccount = new PublicAccount();
                try {
                    BeanUtils.copyProperties(publicAccount, map);
                    publicAccountMap.put(publicAccount.getAppID(), publicAccount);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }

            distributedConfig.setPublicAccountMap(publicAccountMap);
            return distributedConfig;
        }
        return null;
    }

    /**
     * 解析 http请求器节点
     *
     * @param root
     *
     * @return
     */
    private String parseHttpClientClassName(Element root) {
        return Dom4jHelper.selectSingleNodeText(root, InitConstans.node_httpClientClassName);
    }


    public static void main(String[] args) throws SAXException, IOException, ParserConfigurationException, DocumentException {
        String xmlFileName = "F:\\workspace\\inte\\easyexchange-parent\\easyexchange-wx\\src\\main\\resources\\initConfig.xml";
        String xsdFileName = "F:\\workspace\\inte\\easyexchange-parent\\easyexchange-wx\\src\\main\\resources\\initConfig.xsd";
//        validator(xmlFileName,xsdFileName);
    }
}
